package com.sohoappspot.sohosched.client;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.sohoappspot.sohosched.client.model.Model;
import com.sohoappspot.sohosched.client.model.RetryTimer;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.HeadElement;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.http.client.UrlBuilder;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.TabBar;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.sohoappspot.sohosched.client.SohoConstants.ViewMode;
import com.sohoappspot.sohosched.client.events.NavigationEvent;
import com.sohoappspot.sohosched.client.events.NavigationHandler;
import com.sohoappspot.sohosched.client.events.NavigationEvent.NavigationEventType;
import com.sohoappspot.sohosched.client.images.SohoImages;
import com.sohoappspot.sohosched.client.widgets.LeftHandMenu;
import com.sohoappspot.sohosched.client.widgets.SohoCalendar;
import com.sohoappspot.sohosched.client.widgets.SohoCustomers;
import com.sohoappspot.sohosched.client.widgets.TitlePanel;

public class SohoScheduler extends RetryTimer implements EntryPoint,
		Model.LoadObserver, Model.StatusObserver {

	/*
	 * The type passed into the {@link
	 * com.google.gwt.sample.showcase.generator.ShowcaseGenerator}.
	 */
	private static final class GeneratorInfo {
	}

	/**
	 * A special version of the ToggleButton that cannot be clicked if down. If
	 * one theme button is pressed, all of the others are depressed.
	 */
	private static class ThemeButton extends ToggleButton {
		private static List<ThemeButton> allButtons = null;

		private String theme;

		public ThemeButton(String theme) {
			super();
			this.theme = theme;
			addStyleName("sc-ThemeButton-" + theme);

			// Add this button to the static list
			if (allButtons == null) {
				allButtons = new ArrayList<ThemeButton>();
				setDown(true);
			}
			allButtons.add(this);
		}

		public String getTheme() {
			return theme;
		}

		@Override
		protected void onClick() {
			if (!isDown()) {
				// Raise all of the other buttons
				for (ThemeButton button : allButtons) {
					if (button != this) {
						button.setDown(false);
					}
				}

				// Fire the click handlers
				super.onClick();
			}
		}
	}

	// The static images used throughout the Showcase.
	public static final SohoImages images = (SohoImages) GWT
			.create(SohoImages.class);

	// The current style theme.
	static String CUR_THEME = SohoConstants.STYLE_THEMES[0];

	// The {@link Application}.
	private ApplicationLayout app = new ApplicationLayout();

	// A mapping of history tokens to their associated menu items.
	private Map<String, TreeItem> itemTokens = new HashMap<String, TreeItem>();

	// A mapping of menu items to the widget display when the item is selected.
	private Map<TreeItem, Composite> itemWidgets = new HashMap<TreeItem, Composite>();

	private final StatusView status = new StatusView();

	/**
	 * This is the entry point method.
	 */
	public void onModuleLoad() {
		// Setup loading flag.
		RootPanel.get().add(status);
		status.showTaskStatus("Loading");

		// Perform call and setup client
		Model.load(this, this);
	}

	public void onModelLoaded(Model model) {
		// Generate the source code and css for the examples
		GWT.create(GeneratorInfo.class);

		// Create the constants
		final SohoConstants constants = (SohoConstants) GWT
				.create(SohoConstants.class);

		// Swap out the style sheets for the RTL versions if needed.
		updateStyleSheets();

		// Create Left Hand Menu
		final LeftHandMenu lhMenu = new LeftHandMenu(model, constants);
		app.getMainMenuPanel().add(lhMenu);

		// Create the application
		// app.setAccordingToUser(model);
		setupTitlePanel(constants);
		setupMainLinks(constants, model);
		// setupOptionsPanel();

		// Setup a history handler to reselect the associate menu item
		final ValueChangeHandler<String> historyHandler = new ValueChangeHandler<String>() {
			public void onValueChange(ValueChangeEvent<String> event) {
				TreeItem item = itemTokens.get(event.getValue());
				if (item == null) {
					item = lhMenu.getMainMenu().getItem(0).getChild(0);
				}

				// Select the associated TreeItem
				lhMenu.getMainMenu().setSelectedItem(item, false);
				lhMenu.getMainMenu().ensureSelectedItemVisible();

				// Show the associated ContentWidget
				displayContentWidget(itemWidgets.get(item));
			}
		};
		History.addValueChangeHandler(historyHandler);

		// Add a handler that sets the content widget when a menu item is
		// selected
		lhMenu.addNavigationHandler(new NavigationHandler() {
			public void onNavigation(NavigationEvent event) {

				TreeItem treeItem = event.getNavigationTreeItem();

				if (event.getNavigationEventType() == NavigationEventType.Changed) {

					// Check if item is in widgets?
					Composite displayItem = itemWidgets.get(treeItem);

					// Add if it isn't
					if (displayItem == null) {
						displayItem = setupContentItemToTree(treeItem, event.getNavigationViewMode());
					}

					// Get Again
					displayItem = itemWidgets.get(treeItem);

					displayContentWidget(displayItem);
				}
			}
		});

		// Show the initial example
		if (History.getToken().length() > 0) {
			History.fireCurrentHistoryState();
		} else {
			// Use the first token available
			TreeItem firstItem = lhMenu.getMainMenu().getItem(0).getChild(0);
			lhMenu.getMainMenu().setSelectedItem(firstItem, false);
			lhMenu.getMainMenu().ensureSelectedItemVisible();
			displayContentWidget(itemWidgets.get(firstItem));
		}

		status.hideTaskStatus();
	}

	private Composite setupContentItemToTree(TreeItem treeItem, SohoConstants.ViewMode viewMode) {
		
		Composite newItem = null;
		
		if(viewMode==ViewMode.Calendar){
			newItem = new SohoCalendar();			
		}

		if(viewMode==ViewMode.Customer){
			newItem = new SohoCustomers();
		}

		itemWidgets.put(treeItem, newItem);
		
		return newItem;

	}

	public void onModelLoadFailed() {
		retryLater();
		status.showErrorStatus();
	}

	/**
	 * Set the content to the {@link ContentWidget}.
	 * 
	 * @param content
	 *            the {@link ContentWidget} to display
	 */
	private void displayContentWidget(Composite content) {
		if (content != null) {
			app.setContent(content);
			//ToDO
			app.setContentTitle(content);
		}else{
			app.setContentTitle(null);
			app.setContent(null);
		}
	}

	/**
	 * Get the token for a given content widget.
	 * 
	 * @return the content widget token.
	 */
	private String getContentWidgetToken(ContentWidget content) {
		String className = content.getClass().getName();
		className = className.substring(className.lastIndexOf('.') + 1);
		return className;
	}

	/**
	 * Get the style name of the reference element defined in the current GWT
	 * theme style sheet.
	 * 
	 * @param prefix
	 *            the prefix of the reference style name
	 * @return the style name
	 */
	private String getCurrentReferenceStyleName(String prefix) {
		String gwtRef = prefix + "-Reference-" + CUR_THEME;
		if (LocaleInfo.getCurrentLocale().isRTL()) {
			gwtRef += "-rtl";
		}
		return gwtRef;
	}

	/**
	 * Create the main links at the top of the application.
	 * 
	 * @param constants
	 *            the constants with text
	 */
	private void setupMainLinks(SohoConstants constants, Model model) {

		if (model.getIsLoggedIn()) {
			// Link to Soho New Features Page
			app.addLink(new HTML(model.getUserNickname()));
		}

		// Link to Soho New Features Page
		app.addLink(new HTML(
				"<a target=\"top\" style=\"color: rgb(168, 0, 0);font-weight: bold;\" href=\""
						+ SohoConstants.SOHO_NEW_FEATURES_HOMEPAGE + "\">"
						+ constants.mainLinkNewFeatures() + "</a>"));

		// Link to Soho New Features Page
		app.addLink(new HTML("<a target=\"top\" href=\""
				+ SohoConstants.SOHO_HELP_AND_INFO_HOMEPAGE + "\">"
				+ constants.mainLinkHelpAndInfo() + "</a>"));

		/*
		 * // Link to GWT Homepage app.addLink(new HTML("<a href=\"" +
		 * SohoConstants.GWT_HOMEPAGE + "\">" + constants.mainLinkHomepage() +
		 * "</a>"));
		 * 
		 * // Link to More Examples app.addLink(new HTML("<a href=\"" +
		 * SohoConstants.GWT_EXAMPLES + "\">" + constants.mainLinkExamples() +
		 * "</a>"));
		 */
		if (model.getIsLoggedIn()) {
			// Link to GWT Homepage
			app.addLink(new HTML("<a href=\"" + model.getLogoutUrl() + "\">"
					+ constants.mainLinkLogout() + "</a>"));
		} else {
			app.addLink(new HTML("<a href=\"" + model.getLoginUrl() + "\">"
					+ constants.mainLinkLogin() + "</a>"));
		}
	}

	/**
	 * Create the options that appear next to the title.
	 */
	private void setupOptionsPanel() {
		VerticalPanel vPanel = new VerticalPanel();
		vPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
		if (LocaleInfo.getCurrentLocale().isRTL()) {
			vPanel.getElement().setAttribute("align", "left");
		} else {
			vPanel.getElement().setAttribute("align", "right");
		}
		app.setOptionsWidget(vPanel);

		// Add the option to change the locale
		final ListBox localeBox = new ListBox();
		String currentLocale = LocaleInfo.getCurrentLocale().getLocaleName();
		if (currentLocale.equals("default")) {
			currentLocale = "en";
		}
		String[] localeNames = LocaleInfo.getAvailableLocaleNames();
		for (String localeName : localeNames) {
			if (!localeName.equals("default")) {
				String nativeName = LocaleInfo
						.getLocaleNativeDisplayName(localeName);
				localeBox.addItem(nativeName, localeName);
				if (localeName.equals(currentLocale)) {
					localeBox.setSelectedIndex(localeBox.getItemCount() - 1);
				}
			}
		}
		localeBox.addChangeHandler(new ChangeHandler() {
			public void onChange(ChangeEvent event) {
				String localeName = localeBox.getValue(localeBox
						.getSelectedIndex());
				UrlBuilder builder = Location.createUrlBuilder().setParameter(
						"locale", localeName);
				Window.Location.replace(builder.buildString());
			}
		});
		HorizontalPanel localeWrapper = new HorizontalPanel();
		localeWrapper.add(new Image(images.locale()));
		localeWrapper.add(localeBox);
		vPanel.add(localeWrapper);

		// Add the option to change the style
		final HorizontalPanel styleWrapper = new HorizontalPanel();
		vPanel.add(styleWrapper);
		for (int i = 0; i < SohoConstants.STYLE_THEMES.length; i++) {
			final ThemeButton button = new ThemeButton(
					SohoConstants.STYLE_THEMES[i]);
			styleWrapper.add(button);
			button.addClickHandler(new ClickHandler() {
				public void onClick(ClickEvent event) {
					// Update the current theme
					CUR_THEME = button.getTheme();

					// Reload the current tab, loading the new theme if
					// necessary
					TabBar bar = ((TabBar) app.getContentTitle());
					bar.selectTab(bar.getSelectedTab());

					// Load the new style sheets
					updateStyleSheets();
				}
			});
		}
	}

	/**
	 * Create the title bar at the top of the application.
	 * 
	 * @param constants
	 *            the constant values to use
	 */
	private void setupTitlePanel(SohoConstants constants) {
		TitlePanel titlePanel2 = new TitlePanel();
		app.setTitleWidget(titlePanel2);
	}

	/**
	 * Update the style sheets to reflect the current theme and direction.
	 */
	private void updateStyleSheets() {
		// Generate the names of the style sheets to include
		String gwtStyleSheet = "gwt/" + CUR_THEME + "/" + CUR_THEME + ".css";
		String showcaseStyleSheet = CUR_THEME + "/SohoScheduler.css";
		if (LocaleInfo.getCurrentLocale().isRTL()) {
			gwtStyleSheet = gwtStyleSheet.replace(".css", "_rtl.css");
			showcaseStyleSheet = showcaseStyleSheet.replace(".css", "_rtl.css");
		}

		// Find existing style sheets that need to be removed
		boolean styleSheetsFound = false;
		final HeadElement headElem = StyleSheetLoader.getHeadElement();
		final List<Element> toRemove = new ArrayList<Element>();
		NodeList<Node> children = headElem.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			Node node = children.getItem(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				Element elem = Element.as(node);
				if (elem.getTagName().equalsIgnoreCase("link")
						&& elem.getPropertyString("rel").equalsIgnoreCase(
								"stylesheet")) {
					styleSheetsFound = true;
					String href = elem.getPropertyString("href");
					// If the correct style sheets are already loaded, then we
					// should have
					// nothing to remove.
					if (!href.contains(gwtStyleSheet)
							&& !href.contains(showcaseStyleSheet)) {
						toRemove.add(elem);
					}
				}
			}
		}

		// Return if we already have the correct style sheets
		if (styleSheetsFound && toRemove.size() == 0) {
			return;
		}

		// Detach the app while we manipulate the styles to avoid rendering
		// issues
		RootPanel.get().remove(app);

		// Remove the old style sheets
		for (Element elem : toRemove) {
			headElem.removeChild(elem);
		}

		// Load the GWT theme style sheet
		String modulePath = GWT.getModuleBaseURL();
		Command callback = new Command() {
			/**
			 * The number of style sheets that have been loaded and executed
			 * this command.
			 */
			private int numStyleSheetsLoaded = 0;

			public void execute() {
				// Wait until all style sheets have loaded before re-attaching
				// the app
				numStyleSheetsLoaded++;
				if (numStyleSheetsLoaded < 2) {
					return;
				}

				// Different themes use different background colors for the body
				// element, but IE only changes the background of the visible
				// content
				// on the page instead of changing the background color of the
				// entire
				// page. By changing the display style on the body element, we
				// force
				// IE to redraw the background correctly.
				RootPanel.getBodyElement().getStyle().setProperty("display",
						"none");
				RootPanel.getBodyElement().getStyle()
						.setProperty("display", "");
				RootPanel.get().add(app);
			}
		};

		StyleSheetLoader.loadStyleSheet(modulePath + gwtStyleSheet,
				getCurrentReferenceStyleName("gwt"), callback);

		// Load the showcase specific style sheet after the GWT theme style
		// sheet so
		// that custom styles supercede the theme styles.
		StyleSheetLoader.loadStyleSheet(modulePath + showcaseStyleSheet,
				getCurrentReferenceStyleName("Application"), callback);
	}

	public void onServerCameBack() {
		status.hideErrorStatus();
	}

	public void onServerWentAway() {
		status.showErrorStatus();
	}

	public void onTaskFinished() {
		status.hideTaskStatus();
	}

	public void onTaskStarted(String description) {
		status.showTaskStatus(description);
	}

	/**
	 * Provides Ui to notify the user of model based events. These include tasks
	 * (like loading a surface) and also errors (like lost communication to the
	 * server).
	 */
	private static class StatusView extends SimplePanel {
		private final DivElement taskStatusElement;

		private final DivElement errorStatusElement;

		public StatusView() {
			final Document document = Document.get();
			final Element element = getElement();
			taskStatusElement = element
					.appendChild(document.createDivElement());
			errorStatusElement = element.appendChild(document
					.createDivElement());
			errorStatusElement.setInnerText("No response from server");

			setStyleName("status-view");
			taskStatusElement.setClassName("status-view-task");
			errorStatusElement.setClassName("status-view-error");

			hideErrorStatus();
			hideTaskStatus();
		}

		/**
		 * Hides the Ui for server communication lost errors.
		 */
		public void hideErrorStatus() {
			UIObject.setVisible(errorStatusElement, false);
		}

		/**
		 * Hides the task status Ui.
		 */
		public void hideTaskStatus() {
			UIObject.setVisible(taskStatusElement, false);
		}

		/**
		 * Displays the Ui for server communication lost errors.
		 */
		public void showErrorStatus() {
			UIObject.setVisible(errorStatusElement, true);
		}

		/**
		 * Displays the the Ui for a task status.
		 * 
		 * @param text
		 *            the text to be displayed
		 */
		public void showTaskStatus(String text) {
			taskStatusElement.setInnerText(text);
			UIObject.setVisible(taskStatusElement, true);
		}
	}

	@Override
	protected void retry() {
		Model.load(this, this);
	}

}
